
package com.hlkj.pay.app.merchant.ext.impl.lakala.utils;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hlkj.pay.app.merchant.ext.impl.lakala.dto.LakalaConfig;
import com.lkl.laop.sdk.Config;
import com.lkl.laop.sdk.Config2;
import com.lkl.laop.sdk.LKLApiClient;
import com.lkl.laop.sdk.LklHttpClientBuilder;
import com.lkl.laop.sdk.exception.SDKException;
import com.lkl.laop.sdk.exception.SDKExceptionEnums;
import com.lkl.laop.sdk.notification.NotificationHandler;
import com.lkl.laop.sdk.request.LklRequest;
import com.lkl.laop.sdk.utils.FileUtils;
import com.lkl.laop.sdk.utils.PemUtil;
import com.lkl.laop.sdk.utils.SM4Util;

/**
 * @author nxj
 * @date 2024/11/10 17:15
 * @description lkl-sdk
 */
public class LKLSDK {

    private static final Logger log = LoggerFactory.getLogger(LKLSDK.class);

    private static final ConcurrentHashMap<String, LKLApiClient> LKL_API_CLIENT_MAP = new ConcurrentHashMap<>(16);

    private static String defaultAppId;

    /**
     * 连接超时时间
     */
    private static final Integer SDK_CONNECT_TIMEOUT = 50000;

    /**
     * 建立连接后超时时间
     */
    private static final Integer SDK_READ_TIMEOUT = 100000;

    /**
     * 读取数据超时时间
     */
    private static final Integer SDK_SOCKET_TIMEOUT = 50000;

    private LKLSDK() {

    }


    /**
     * 配置文件--初始化SDK
     *
     * @param configPath
     *            properties配置文件地址
     * @return boolean
     * @throws SDKException
     *             sdk异常
     */
    public static boolean init(LakalaConfig lakalaConfig, String serverUrl) throws SDKException {
        Config config = new Config();
        config.setAppId(lakalaConfig.getAppId());
        config.setSerialNo(lakalaConfig.getSerialNo());
        config.setPriKeyPath(lakalaConfig.getPriKeyPath());
        config.setLklCerPath(lakalaConfig.getLklNotifyCerPath());
        config.setLklNotifyCerPath(lakalaConfig.getLklNotifyCerPath());
        config.setConnectTimeout(SDK_CONNECT_TIMEOUT);
        config.setReadTimeout(SDK_READ_TIMEOUT);
        config.setSocketTimeout(SDK_SOCKET_TIMEOUT);
        config.setServerUrl(serverUrl);
        config.setSm4Key(lakalaConfig.getSm4Key());
        return init(config);
    }

    /**
     * Config--初始化SDK
     *
     * @param config
     *            配置项
     * @return boolean
     * @throws SDKException
     *             sdk异常
     */
    public static boolean init(Config config) throws SDKException {
        Config2 config2 = new Config2();
        config2.setAppId(config.getAppId());
        config2.setSerialNo(config.getSerialNo());
        config2.setPriKey(FileUtils.readFile(config.getPriKeyPath()));
        config2.setLklCer(FileUtils.readFile(config.getLklCerPath()));
        config2.setLklNotifyCer(FileUtils.readFile(config.getLklNotifyCerPath()));
        config2.setConnectTimeout(config.getConnectTimeout());
        config2.setReadTimeout(config.getReadTimeout());
        config2.setSocketTimeout(config.getSocketTimeout());
        config2.setServerUrl(config.getServerUrl());
        config2.setSm4Key(config.getSm4Key());
        config2.setHttpClientConnectionManager(config.getHttpClientConnectionManager());
        return init(config2);
    }

    /**
     * Config2--初始化SDK
     *
     * @param config
     *            配置项-支持公私钥、证书等字符串注入
     * @return boolean
     */
    public static boolean init(Config2 config) {
        try {
            if (StringUtils.isBlank(config.getAppId())) {
                log.error("SDK000009 拉卡拉appId");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isBlank(config.getSerialNo())) {
                log.error("SDK000009 商户证书序列号");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isBlank(config.getPriKey())) {
                log.error("SDK000009 商户私钥信息");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isBlank(config.getLklCer())) {
                log.error("SDK000009 拉卡拉平台证书");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isBlank(config.getLklNotifyCer())) {
                log.error("SDK000009 拉卡拉平台通知验签证书");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isBlank(config.getServerUrl())) {
                log.error("SDK000009 拉卡拉serverUrl");
                throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
            }
            if (StringUtils.isNotEmpty(config.getSm4Key())) {
                if (!SM4Util.verifyKey(config.getSm4Key())) {
                    log.error("SDK000009 拉卡拉报文加密对称性密钥");
                    throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
                }
            }

            // http请求时间参数配置
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(Objects.isNull(config.getConnectTimeout()) ? SDK_CONNECT_TIMEOUT : config.getConnectTimeout())
                    .setConnectionRequestTimeout(Objects.isNull(config.getReadTimeout()) ? SDK_READ_TIMEOUT : config.getReadTimeout())
                    .setSocketTimeout(Objects.isNull(config.getSocketTimeout()) ? SDK_SOCKET_TIMEOUT : config.getSocketTimeout()).build();
            // lkl-sm4参数配置
            SM4Util sm4Util = StringUtils.isNotEmpty(config.getSm4Key()) ? new SM4Util(Base64.decode(config.getSm4Key())) : null;
            // lkl-商户私钥配置
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPriKey().getBytes(StandardCharsets.UTF_8)));
            // lkl-证书配置
            X509Certificate paySignatureCertificate = PemUtil.loadCertificate(new ByteArrayInputStream(config.getLklCer().getBytes(StandardCharsets.UTF_8)));
            ArrayList<X509Certificate> listCertificates = new ArrayList<>();
            listCertificates.add(paySignatureCertificate);
            // lkl-回调证书配置
            X509Certificate paySignatureNotifyCertificate = PemUtil.loadCertificate(new ByteArrayInputStream(config.getLklNotifyCer().getBytes(StandardCharsets.UTF_8)));
            // http客户端配置
            CloseableHttpClient build = LklHttpClientBuilder.create(config.getHttpClientConnectionManager())
                    .withMerchant(config.getAppId(), config.getSerialNo(), merchantPrivateKey).withLklpay(listCertificates).setDefaultRequestConfig(requestConfig).build();
            NotificationHandler notificationHandler = new NotificationHandler(paySignatureNotifyCertificate);

            // 设置默认appId
            if (defaultAppId == null) {
                synchronized (LKLSDK.class) {
                    if (defaultAppId == null) {
                        defaultAppId = config.getAppId();
                    }
                }
            }
            // 设置lkl-api客户端
            LKL_API_CLIENT_MAP.put(config.getAppId(), new LKLApiClient(config.getAppId(), build, notificationHandler, sm4Util, config.getServerUrl()));

        }
        catch (Exception e) {
            log.error("初始化失败...appId={}", config.getAppId(), e);
            return false;
        }
        log.info("初始化成功...appId={}", config.getAppId());
        return true;
    }

    /**
     * SDK post 请求 -默认无需对参数加解密
     *
     * @param request
     *            请求实体
     * @throws SDKException
     *             sdk异常
     */
    public static String httpPost(LklRequest request) throws SDKException {
        return httpPost(request, false, false);
    }

    /**
     * SDK post 请求 -需要加解密
     *
     * @param request
     *            请求体
     * @param reqEncrypt
     *            请求是否需要加密
     * @param respDecrypt
     *            响应是否需要解密
     * @throws SDKException
     *             sdk异常
     */
    public static String httpPost(LklRequest request, boolean reqEncrypt, boolean respDecrypt) throws SDKException {

        // 获取lkl-api-client
        LKLApiClient lklApiClient = obtainLklApiClient(request.getLklAppId());
        boolean needCrypt = reqEncrypt || respDecrypt;
        // 加解密需要验证SM4Util是否已注册
        if (needCrypt && lklApiClient.getSm4Util() == null) {
            throw new SDKException(SDKExceptionEnums.SM4_INIT_FAIL, "appId=" + lklApiClient.getAppId());
        }
        String url = request.getFunctionCode().getUrl();
        String body;
        if (reqEncrypt) {
            body = lklApiClient.getSm4Util().encrypt(request.toBody());
        }
        else {
            body = request.toBody();
        }

        String response = httpPost(lklApiClient.getSdkServerUrl() + url, body, lklApiClient);

        if (respDecrypt) {
            return lklApiClient.getSm4Util().decrypt(response);
        }
        return response;
    }

    /**
     * SDK post 请求
     *
     * @param fullUrl
     *            API url 全路径
     * @param body
     *            报文内容JSON
     * @throws SDKException
     *             sdk异常
     */
    public static String httpPost(String fullUrl, String body) throws SDKException {
        try {
            LKLApiClient lklApiClient = obtainLklApiClient(defaultAppId);
            log.info("httpPost url:{}", fullUrl);
            HttpPost httpPost = new HttpPost(fullUrl);
            InputStream stream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
            InputStreamEntity reqEntity = new InputStreamEntity(stream);
            reqEntity.setContentType("application/json;charset=UTF-8");
            httpPost.setEntity(reqEntity);
            httpPost.addHeader("Accept", "application/json");
            CloseableHttpResponse response = lklApiClient.getHttpClient().execute(httpPost);
            if (response == null) {
                log.error("response is null.");
                throw new SDKException(SDKExceptionEnums.RES_IS_NULL);
            }
            HttpEntity entity = response.getEntity();
            response.close();
            return EntityUtils.toString(entity, StandardCharsets.UTF_8);
        }
        catch (SDKException se) {
            throw se;
        }
        catch (Exception e) {
            log.error("异常 --> url:{},err -->", fullUrl, e);
            throw new SDKException(SDKExceptionEnums.ERROR.getCode(), e.getMessage() != null ? e.getMessage() : (e.getCause() != null ? e.getCause().getMessage() : ""));
        }
    }

    /**
     * SDK post 请求 - 多配置场景需指定appId
     *
     * @param fullUrl
     *            API url 路径
     * @param body
     *            报文内容
     * @param appId
     *            lkl-app-id 必传
     * @throws SDKException
     */
    public static String httpPost(String fullUrl, String body, String appId) throws SDKException {
        try {
            LKLApiClient lklApiClient = obtainLklApiClient(appId);
            log.info("httpPost url:{}", fullUrl);
            HttpPost httpPost = new HttpPost(fullUrl);
            InputStream stream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
            InputStreamEntity reqEntity = new InputStreamEntity(stream);
            reqEntity.setContentType("application/json;charset=UTF-8");
            httpPost.setEntity(reqEntity);
            httpPost.addHeader("Accept", "application/json");
            CloseableHttpResponse response = lklApiClient.getHttpClient().execute(httpPost);
            if (response == null) {
                log.error("response is null.");
                throw new SDKException(SDKExceptionEnums.RES_IS_NULL);
            }
            HttpEntity entity = response.getEntity();
            response.close();
            return EntityUtils.toString(entity, StandardCharsets.UTF_8);
        }
        catch (SDKException se) {
            throw se;
        }
        catch (Exception e) {
            log.error("异常 --> url:{},err -->", fullUrl, e);
            throw new SDKException(SDKExceptionEnums.ERROR.getCode(), e.getMessage() != null ? e.getMessage() : (e.getCause() != null ? e.getCause().getMessage() : ""));
        }
    }

    /**
     * lkl通知回调处理
     *
     * @param request
     *            HttpServletRequest
     * @return 验签解析后的json报文
     * @throws SDKException
     *             sdk异常
     */
    public static String notificationHandle(HttpServletRequest request) throws SDKException {
        return notificationHandle(request, defaultAppId);
    }

    /**
     * lkl通知回调处理
     *
     * @param body
     *            请求报文
     * @param authorization
     *            授权信息
     * @throws SDKException
     *             sdk异常
     */
    public static void notificationHandle(String body, String authorization) throws SDKException {
        notificationHandle(body, authorization, defaultAppId);
    }

    /**
     * lkl通知回调处理 -指定appId
     *
     * @param request
     *            HttpServletRequest
     * @param appId
     *            指定appId配置
     * @throws SDKException
     *             sdk异常
     */
    public static String notificationHandle(HttpServletRequest request, String appId) throws SDKException {
        LKLApiClient lklApiClient = obtainLklApiClient(appId);
        return lklApiClient.getNotificationHandler().parse(request);
    }

    /**
     * lkl通知回调处理 -指定appId
     *
     * @param body
     *            请求报文
     * @param appId
     *            指定appId配置
     * @param authorization
     *            授权信息
     * @throws SDKException
     *             sdk异常
     */
    public static void notificationHandle(String body, String authorization, String appId) throws SDKException {
        LKLApiClient lklApiClient = obtainLklApiClient(appId);
        lklApiClient.getNotificationHandler().validate(body, authorization);
    }

    /**
     * SM4报文加密
     *
     * @param body
     *            json字符串
     * @return 加密后字符串
     * @throws SDKException
     *             sdk异常
     */
    public static String sm4Encrypt(String body) throws SDKException {
        return sm4Encrypt(body, defaultAppId);
    }

    /**
     * SM4报文加密 -指定appId配置
     *
     * @param body
     *            json字符串
     * @return 加密后字符串
     * @throws SDKException
     *             sdk异常
     */
    public static String sm4Encrypt(String body, String appId) throws SDKException {
        LKLApiClient lklApiClient = obtainLklApiClient(appId);
        if (lklApiClient.getSm4Util() == null) {
            throw new SDKException(SDKExceptionEnums.SM4_INIT_FAIL, "appId=" + lklApiClient.getAppId());
        }
        if (StringUtils.isBlank(body)) {
            throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
        }
        return lklApiClient.getSm4Util().encrypt(body);
    }

    /**
     * SM4响应解密
     *
     * @param body
     *            加密字符串
     * @return 解密后的json字符串
     * @throws SDKException
     *             sdk异常
     */
    public static String sm4Decrypt(String body) throws SDKException {
        return sm4Decrypt(body, defaultAppId);
    }

    /**
     * SM4响应解密 -指定appId配置
     *
     * @param body
     *            加密字符串
     * @return 解密后的json字符串
     * @throws SDKException
     *             sdk异常
     */
    public static String sm4Decrypt(String body, String appId) throws SDKException {
        LKLApiClient lklApiClient = obtainLklApiClient(appId);
        if (lklApiClient.getSm4Util() == null) {
            throw new SDKException(SDKExceptionEnums.SM4_INIT_FAIL, "appId=" + lklApiClient.getAppId());
        }
        if (StringUtils.isBlank(body)) {
            throw new SDKException(SDKExceptionEnums.CHECK_FAIL);
        }
        return lklApiClient.getSm4Util().decrypt(body);
    }

    /**
     * @param fullUrl
     *            接口地址完整url
     * @param body
     *            请求参数json
     * @param lklApiClient
     *            lkl-api客户端- 必传
     * @throws SDKException
     *             sdk异常
     */
    private static String httpPost(String fullUrl, String body, LKLApiClient lklApiClient) throws SDKException {
        try {
            log.info("httpPost url:{}", fullUrl);
            HttpPost httpPost = new HttpPost(fullUrl);
            InputStream stream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
            InputStreamEntity reqEntity = new InputStreamEntity(stream);
            reqEntity.setContentType("application/json;charset=UTF-8");
            httpPost.setEntity(reqEntity);
            httpPost.addHeader("Accept", "application/json");
            CloseableHttpResponse response = lklApiClient.getHttpClient().execute(httpPost);
            if (response == null) {
                log.error("response is null.");
                throw new SDKException(SDKExceptionEnums.RES_IS_NULL);
            }
            HttpEntity entity = response.getEntity();
            response.close();
            return EntityUtils.toString(entity, StandardCharsets.UTF_8);
        }
        catch (SDKException se) {
            throw se;
        }
        catch (Exception e) {
            log.error("异常 --> url:{},err -->", fullUrl, e);
            throw new SDKException(SDKExceptionEnums.ERROR.getCode(), e.getMessage() != null ? e.getMessage() : (e.getCause() != null ? e.getCause().getMessage() : ""));
        }
    }

    /**
     * 获取lkl-api-client,如果未指定appId,则取默认appId
     *
     * @param lklAppId
     *            拉卡拉appId
     * @return lkl-api-client
     * @throws SDKException
     *             sdk异常
     */
    private static LKLApiClient obtainLklApiClient(String lklAppId) throws SDKException {
        String appId = StringUtils.isBlank(lklAppId) ? defaultAppId : lklAppId;
        if (StringUtils.isBlank(appId)) {
            throw new SDKException(SDKExceptionEnums.SDK_NOT_INIT);
        }
        LKLApiClient lklApiClient = LKL_API_CLIENT_MAP.get(appId);

        if (lklApiClient == null) {
            throw new SDKException(SDKExceptionEnums.SDK_APPID_NOT_INIT, "appId=" + appId);
        }
        return lklApiClient;

    }
}
